<!-- directive:breadcrumb ES6学习笔记 -->
<!-- directive:title 异步编程对 JavaScript 语言太重要。 -->
<div class='panel-body'>
    <p>JavaScript 语言的执行环境是“单线程”的，如果没有异步编程，根本没法用，非卡死不可。</p>
    <p>ES6 诞生以前，异步编程的方法，大概有下面四种。回调函数/事件监听/发布/订阅/Promise对象</p>
    <p></p>
</div>
<!-- directive:title Promise -->
<div class='panel-body'>
    <p>回调函数本身并没有问题，它的问题出现在多个回调函数嵌套。假定读取A文件之后，再读取B文件</p>
    <script type="text/xian" ui-bs>
        fs.readFile(fileA, 'utf-8', function (err, data) {
            fs.readFile(fileB, 'utf-8', function (err, data) {
                // ...
            });
        });
    </script>
    <p>不难想象，如果依次读取两个以上的文件，就会出现多重嵌套。代码不是纵向发展，而是横向发展，很快就会乱成一团，无法管理。</p>
    <p>这种情况就称为"回调函数地狱"（callback hell）。</p>
    <p>Promise 对象就是为了解决这个问题而提出的。</p>
    <p>它不是新的语法功能，而是一种新的写法，允许将回调函数的嵌套，改成链式调用。采用 Promise，连续读取多个文件</p>
    <script type="text/xian" ui-bs>
        var readFile = require('fs-readfile-promise');
        readFile(fileA)
        .then(function (data) { console.log(data.toString()); })
        .then(function () { return readFile(fileB); })
        .then(function (data) { console.log(data.toString()); })
        .catch(function (err) { console.log(err); });
    </script>
    <p>Promise 提供then方法加载回调函数，catch方法捕捉执行过程中抛出的错误。</p>
    <p>可以看到，Promise 的写法只是回调函数的改进，使用then方法以后，异步任务的两段执行看得更清楚了，除此以外，并无新意。</p>
    <p>Promise 的最大问题是代码冗余，原来的任务被 Promise 包装了一下，不管什么操作，一眼看去都是一堆then，原来的语义变得很不清楚。</p>
    <p>那么，有没有更好的写法呢？</p>
    <p></p>
</div>
<!-- directive:title 协程 -- 协程 -- 协程 -->
<div class='panel-body'>
    <pre better-pre>
        传统的编程语言，早有异步编程的解决方案（其实是多任务的解决方案）。
        其中有一种叫做"协程"（coroutine），意思是多个线程互相协作，完成异步任务。
        协程有点像函数，又有点像线程。它的运行流程大致如下。
        第一步，协程A开始执行。
        第二步，协程A执行到一半，进入暂停，执行权转移到协程B。
        第三步，（一段时间后）协程B交还执行权。
        第四步，协程A恢复执行。
    </pre>
    <p>上面流程的协程A，就是异步任务，因为它分成两段（或多段）执行。</p>
    <script type="text/xian" ui-bs>
        function* asyncJob() {
            // ...其他代码
            var f = yield readFile(fileA);
            // ...其他代码
        }
    </script>
    <p>上面代码的函数asyncJob是一个协程，它的奥妙就在其中的yield命令。</p>
    <p>它表示执行到此处，执行权将交给其他协程。也就是说，yield命令是异步两个阶段的分界线。</p>
    <p>协程遇到yield命令就暂停，等到执行权返回，再从暂停的地方继续往后执行。</p>
    <p>它的最大优点，就是代码的写法非常像同步操作，如果去除yield命令，简直一模一样。</p>
    <p></p>
</div>
<!-- directive:title Generator函数 -->
<div class='panel-body'>
    <p>Generator函数是协程在 ES6 的实现，最大特点就是可以交出函数的执行权（即暂停执行）。</p>
    <p>整个 Generator 函数就是一个封装的异步任务，或者说是异步任务的容器。</p>
    <p>异步操作需要暂停的地方，都用yield语句注明。</p>
    <script type="text/xian" ui-bs>
        function* gen(x) {
            var y = yield x + 2;
            return y;
        }
        var g = gen(1);
        g.next() // { value: 3, done: false }
        g.next() // { value: undefined, done: true }
        // 调用 Generator 函数，会返回一个内部指针（即遍历器）g。
    </script>
    <p>这是 Generator 函数不同于普通函数的另一个地方，即执行它不会返回结果，返回的是指针对象。</p>
    <p>调用指针g的next方法，会移动内部指针（即执行异步任务的第一段），指向第一个遇到的yield语句，上例是执行到x + 2为止。</p>
    <p>换言之，next方法的作用是分阶段执行Generator函数。</p>
    <p>每次调用next方法，会返回一个对象，表示当前阶段的信息（value属性和done属性）。</p>
    <p>value属性是yield语句后面表达式的值，表示当前阶段的值；done属性是一个布尔值，表示 Generator 函数是否执行完毕，即是否还有下一个阶段。</p>
    <p>Generator 函数可以暂停执行和恢复执行，这是它能封装异步任务的根本原因。</p>
    <p>它还有两个特性，使它可以作为异步编程的完整解决方案：函数体内外的数据交换和错误处理机制。</p>
    <p>next返回值的 value 属性，是 Generator 函数向外输出数据；next方法还可以接受参数，向 Generator 函数体内输入数据。</p>
    <script type="text/xian" ui-bs>
        function* gen(x){
            var y = yield x + 2;
            return y;
        }
        var g = gen(1);
        g.next() // { value: 3, done: false }
        g.next(2) // { value: 2, done: true }
        // 第一个next方法的value属性，返回表达式x + 2的值3。
        // 第二个next方法带有参数2，这个参数可以传入 Generator 函数，作为上个阶段异步任务的返回结果，被函数体内的变量y接收。
        // 因此，这一步的value属性，返回的就是2（变量y的值）。
    </script>
    <p>Generator 函数内部还可以部署错误处理代码，捕获函数体外抛出的错误。</p>
    <script type="text/xian" ui-bs>
        function* gen(x){
            try {
                var y = yield x + 2;
            } catch (e){
                console.log(e);
            }
            return y;
        }
        var g = gen(1);
        g.next();
        g.throw('出错了');
        // 出错了
    </script>
    <p>Generator 函数体外，使用指针对象的throw方法抛出的错误，可以被函数体内的try...catch代码块捕获。</p>
    <p>这意味着，出错的代码与处理错误的代码，实现了时间和空间上的分离，这对于异步编程无疑是很重要的。</p>
    <p></p>
</div>
<!-- directive:title 异步任务的封装 -->
<div class='panel-body'>
    <p>就像前面说过的，这段代码非常像同步操作，除了加上了yield命令。</p>
    <script type="text/xian" ui-bs>
        var fetch = require('node-fetch');
        function* gen(){
            var url = 'https://api.github.com/users/github';
            var result = yield fetch(url);
            console.log(result.bio);
        }
        var g = gen();
        var result = g.next();
        result.value.then(function(data){
            return data.json();
        }).then(function(data){
            g.next(data);
        });
    </script>
    <p>首先执行 Generator 函数，获取遍历器对象，然后使用next方法（第二行），执行异步任务的第一阶段。</p>
    <p>由于Fetch模块返回的是一个 Promise 对象，因此要用then方法调用下一个next方法。</p>
    <p>虽然 Generator 函数将异步操作表示得很简洁，但是流程管理却不方便（即何时执行第一阶段、何时执行第二阶段）。</p>
    <p></p>
</div>
<!-- directive:title Thunk 函数是自动执行 Generator 函数的一种方法。 -->
<div class='panel-body'>
    <p>Thunk 函数早在上个世纪 60 年代就诞生了。</p>
    <p>编译器的“传名调用”实现，往往是将参数放到一个临时函数之中，再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。</p>
    <script type="text/xian" ui-bs>
        function f(m) {
            return m * 2;
        }
        f(x + 5);
        // 等同于
        var thunk = function () {
            return x + 5;
        };
        function f(thunk) {
            return thunk() * 2;
        }
        // 函数 f 的参数x + 5被一个函数替换了。
        // 凡是用到原参数的地方，对Thunk函数求值即可。
    </script>
    <p>这就是 Thunk 函数的定义，它是“传名调用”的一种实现策略，用来替换某个表达式。</p>
    <p>在 JavaScript 语言中，Thunk 函数替换的不是表达式，而是多参数函数，将其替换成一个只接受回调函数作为参数的单参数函数。</p>
    <script type="text/xian" ui-bs>
        // 正常版本的readFile（多参数版本）
        fs.readFile(fileName, callback);
        // Thunk版本的readFile（单参数版本）
        var Thunk = function (fileName) {
            return function (callback) {
                return fs.readFile(fileName, callback);
            };
        };
        var readFileThunk = Thunk(fileName);
        readFileThunk(callback);
    </script>
    <p>经过转换器处理，它变成了一个单参数函数，只接受回调函数作为参数。这个单参数版本，就叫做 Thunk 函数。</p>
    <p>任何函数，只要参数有回调函数，就能写成 Thunk 函数的形式。下面是一个简单的 Thunk 函数转换器。</p>
    <script type="text/xian" ui-bs>
        // ES5版本
        var Thunk = function(fn){
            return function (){
                var args = Array.prototype.slice.call(arguments);
                return function (callback){
                    args.push(callback);
                    return fn.apply(this, args);
                }
            };
        };
        // ES6版本
        const Thunk = function(fn) {
            return function (...args) {
                return function (callback) {
                    return fn.call(this, ...args, callback);
                }
            };
        };
        // 使用上面的转换器，生成fs.readFile的 Thunk 函数。
        var readFileThunk = Thunk(fs.readFile);
        readFileThunk(fileA)(callback);
    </script>
    <script type="text/xian" ui-bs>
        function f(a, cb) { cb(a); }
        const ft = Thunk(f);
        ft(1)(console.log) // 1
    </script>
    <p></p>
</div>
<!-- directive:title Thunkify模块??????????????? -->
<div class='panel-body'>
    <pre better-pre>
        生产环境的转换器，建议使用 Thunkify 模块。
        首先是安装。
        $ npm install thunkify
        使用方式如下。
        var thunkify = require('thunkify');
        var fs = require('fs');
        var read = thunkify(fs.readFile);
        read('package.json')(function(err, str){
            // ...
        });
        Thunkify 的源码与上一节那个简单的转换器非常像。
        function thunkify(fn) {
            return function() {
                var args = new Array(arguments.length);
                var ctx = this;
                for (var i = 0; i < args.length; ++i) {
                    args[i] = arguments[i];
                }
                return function (done) {
                    var called;
                    args.push(function () {
                        if (called) return;
                        called = true;
                        done.apply(null, arguments);
                    });
                    try {
                        fn.apply(ctx, args);
                    } catch (err) {
                        done(err);
                    }
                }
            }
        };
        它的源码主要多了一个检查机制，变量called确保回调函数只运行一次。这样的设计与下文的 Generator 函数相关。请看下面的例子。
        function f(a, b, callback){
            var sum = a + b;
            callback(sum);
            callback(sum);
        }
        var ft = thunkify(f);
        var print = console.log.bind(console);
        ft(1, 2)(print);
        // 3
        上面代码中，由于thunkify只允许回调函数执行一次，所以只输出一行结果。
    </pre>
    <p></p>
</div>
<!-- directive:title Generator 函数的流程管理 -->
<div class='panel-body'>
    <p>你可能会问， Thunk 函数有什么用？回答是以前确实没什么用，但是 ES6 有了 Generator 函数，Thunk 函数现在可以用于 Generator 函数的自动流程管理。</p>
    <p>Generator 函数可以自动执行。</p>
    <script type="text/xian" ui-bs>
        function* gen() { // ... // }
        var g = gen();
        var res = g.next();
        while(!res.done){
            console.log(res.value);
            res = g.next();
        }
        // Generator 函数gen会自动执行完所有步骤。
    </script>
    <p>但是，这不适合异步操作。如果必须保证前一步执行完，才能执行后一步，上面的自动执行就不可行。</p>
    <script type="text/xian" ui-bs>
        var fs = require('fs');
        var thunkify = require('thunkify');
        var readFileThunk = thunkify(fs.readFile);
        var gen = function* (){
            var r1 = yield readFileThunk('/etc/fstab');
            console.log(r1.toString());
            var r2 = yield readFileThunk('/etc/shells');
            console.log(r2.toString());
        };
    </script>
    <p>yield命令用于将程序的执行权移出 Generator 函数，那么就需要一种方法，将执行权再交还给 Generator 函数。</p>
    <p>这种方法就是 Thunk 函数，因为它可以在回调函数里，将执行权交还给 Generator 函数。</p>
    <p>为了便于理解，我们先看如何手动执行上面这个 Generator 函数。</p>
    <script type="text/xian" ui-bs>
        var g = gen();
        var r1 = g.next();
        r1.value(function (err, data) {
            if (err) throw err;
            var r2 = g.next(data);
            r2.value(function (err, data) {
                if (err) throw err;
                g.next(data);
            });
        });
    </script>
    <p>上面代码中，变量g是 Generator 函数的内部指针，表示目前执行到哪一步。next方法负责将指针移动到下一步，并返回该步的信息（value属性和done属性）。
        仔细查看上面的代码，可以发现 Generator 函数的执行过程，其实是将同一个回调函数，反复传入next方法的value属性。这使得我们可以用递归来自动完成这个过程。</p>
    <p></p>
</div>
<!-- directive:title Thunk 函数的自动流程管理 -->
<div class='panel-body'>
    <p>Thunk 函数真正的威力，在于可以自动执行 Generator 函数。</p>
    <script type="text/xian" ui-bs>
        function run(fn) {
            var gen = fn();
            function next(err, data) {
                var result = gen.next(data);
                if (result.done) return;
                result.value(next);
            }
            next();
        }
        function* g() {
            // ...
        }
        run(g); // Generator 函数的自动执行器。
    </script>
    <p>next函数先将指针移到 Generator 函数的下一步（gen.next方法），然后判断 Generator 函数是否结束（result.done属性），如果没结束，就将next函数再传入 Thunk 函数（result.value属性），否则就直接退出。</p>
    <p>有了这个执行器，执行 Generator 函数方便多了。不管内部有多少个异步操作，直接把 Generator 函数传入run函数即可。</p>
    <p>当然，前提是每一个异步操作，都要是 Thunk 函数，也就是说，跟在yield命令后面的必须是 Thunk 函数。</p>
    <script type="text/xian" ui-bs>
        var g = function* (){
            var f1 = yield readFileThunk('fileA');
            var f2 = yield readFileThunk('fileB');
            // ...
            var fn = yield readFileThunk('fileN');
        };
        run(g);
    </script>
    <p>上面代码中，函数g封装了n个异步的读取文件操作，只要执行run函数，这些操作就会自动完成。这样一来，异步操作不仅可以写得像同步操作，而且一行代码就可以执行。</p>
    <p>Thunk 函数并不是 Generator 函数自动执行的唯一方案。因为自动执行的关键是，必须有一种机制，自动控制 Generator 函数的流程，接收和交还程序的执行权。回调函数可以做到这一点，Promise 对象也可以做到这一点。</p>
    <p></p>
</div>
